import tkinter as tk
import tkinter.messagebox
import socket
import winsound
import os
import pickle
import time
s=socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
d={}#name:ip
vd={}#ip:name
mslist=[]
#start-ip

our_name=""
#start-ip
def loading():
    global our_name,d,mslist,vd
    try:
        with open("chater.cfg",mode="rb") as f:
            listemp=pickle.load(f)
            if not type(listemp)==list and len(listemp)==3:
                return False
            if listemp[0]=="": return False
            our_name=listemp[0]
            d=listemp[1]
            d["全局广播"]=".".join(our_name.split(".")[:-1])+".255"
            vd={v: k for k, v in d.items()}
            mslist=listemp[2]
        return True
    except:
        return False


def dumping(flag=True):
    if not flag:
        with open("chater.cfg",mode="wb") as f:
            pickle.dump([our_name,d,tuple()],f)
        return
    with open("chater.cfg",mode="wb") as f:
        pickle.dump([our_name,d,listm.get(0,"end")],f)


def is_valid_ipv4_address(ip_str):
    sep = ip_str.split('.')
    if len(sep) != 4:
        return False
    for x in sep:
        try:
            int_x = int(x)
            if int_x < 0 or int_x > 255:
                return False
        except ValueError as e:
            return False
    return True

def binding(ip):
    if is_valid_ipv4_address(ip):
        s.bind((ip,8080))
        s.setblocking(0)
        
if not loading():
    def getsip():
        global our_name,d,vd
        our_name=e.get()
        if is_valid_ipv4_address(our_name):
            try:
                binding(our_name)
                d={"全局广播":".".join(our_name.split(".")[:-1])+".255"}
                vd={v: k for k, v in d.items()}

            except:
                tkinter.messagebox.showerror("错误","这不是你的IP地址！")
            else:
                dumping(False)
                nroot.destroy()
        else:
            tkinter.messagebox.showerror("错误","IP格式错误！")
    nroot=tk.Tk()
    nroot.title("")
    tk.Label(nroot,text="本机IP地址:").grid(row=0,column=0,rowspan=1,columnspan=3)
    e=tk.Entry(nroot)
    e.grid(row=1,column=0,rowspan=1,columnspan=2)
    b=tk.Button(nroot,text="确定",command=getsip)
    b.grid(row=1,column=2,rowspan=1,columnspan=1)
    nroot.mainloop()
else:
    binding(our_name)



wtest=lambda x:"TTL" in os.popen("ping /n 1 "+x).read()
def fr():
    def nd():
        global vd
        name=e1.get()
        cname=e2.get()
        if is_valid_ipv4_address(name):
            if not wtest(name):
                tkinter.messagebox.showwarning("警告","IP未连通，对方可能不在线！")
            listb.insert("end",cname)
            d[cname]=name
            vd={v: k for k, v in d.items()}
            dumping()
            nroot.destroy()
        else:
            tkinter.messagebox.showerror("错误","IP格式错误！")
    nroot=tk.Toplevel()
    nroot.title("")
    nroot.geometry("+"+str(root.winfo_x()+50)+"+"+str(root.winfo_y()+50))
    nroot.grab_set()
    
    tk.Label(nroot,text="IP地址:").grid(row=0,column=0,rowspan=1,columnspan=3)
    e1=tk.Entry(nroot)
    e1.grid(row=1,column=0,rowspan=1,columnspan=3)
    
    tk.Label(nroot,text="计算机名（自定义）:").grid(row=2,column=0,rowspan=1,columnspan=3)
    e2=tk.Entry(nroot)
    e2.grid(row=3,column=0,rowspan=1,columnspan=3)
    
    b=tk.Button(nroot,text="确定",command=nd)
    b.grid(row=4,column=0,rowspan=1,columnspan=3)
    nroot.mainloop()
def reload():
    try:
        while True:
            data, addr = s.recvfrom(65536)
            if data.decode("utf-8")=="&return&":
                s.sendto(b"&ok&",addr)
                continue
            if addr[0]==our_name and "[广播]" in data.decode("utf-8"):
                continue
            pushlt(data,addr)
            
    except:
        pass
    finally:
        root.after(250, reload)

def pushlt(data,addr):
    winsound.Beep(1000,500)
    if addr[0] in vd: listm.insert("end", vd[addr[0]] + ":" + data.decode("utf-8"))
    else: listm.insert("end", addr[0] + ":" + data.decode("utf-8"))
    dumping()
def sent():
    text=e.get()
    try:
        addr=d[listb.get(listb.curselection())]
        e.delete("0","end")
    except:
        tkinter.messagebox.showerror("错误","您似乎没选收件人...")
    else:
        listm.insert("end","我=>"+vd[addr]+":"+text)
        if addr.split(".")[-1]=="255": text="[广播]"+text
        s.sendto(text.encode("utf-8"),(addr,8080))
        dumping()
def dell():
    del d[listb.get("active")]
    vd={v: k for k, v in d.items()}
    listb.delete("active")
    dumping()
def more():
    def clitest():
        try:
            addrn=d[listb.get(listb.curselection())]
            assert addrn!=".".join(our_name.split(".")[:-1])+".255"
            assert addrn!=our_name
        except AssertionError as e:
            tkinter.messagebox.showerror("错误","您似乎选择了广播或自己...")
            nroot.destroy()
        except:
            tkinter.messagebox.showerror("错误","您似乎没选收件人...")
            nroot.destroy()
        else:
            s.sendto(b"&return&",(addrn,8080))
            oflag=True
            for i in range(4):
                try:
                    data, addr = s.recvfrom(65536)
                except:
                    pass
                else:
                    if data.decode("utf-8")=="&ok&" and addr[0]==addrn:
                        oflag=False
                        break
                    pushlt(data,addr)
                finally:
                    time.sleep(0.1)
            if oflag:
                tkinter.messagebox.showerror("错误","对方似乎没有打开接收端或网络中断")
            else:
                tkinter.messagebox.showinfo("提示","对方接收端工作正常")
            nroot.destroy()
    def clearing():
        listm.delete(0,"end")
        mslist=[]
        dumping()
        nroot.destroy()
    def resetcast():
        d["全局广播"]=".".join(our_name.split(".")[:-1])+".255"
        vd={v: k for k, v in d.items()}
        listb.delete(0,"end")
        for k in d:
            listb.insert("end",k)
        dumping()
        nroot.destroy()
    nroot=tk.Toplevel()
    nroot.title("")
    nroot.geometry("+"+str(root.winfo_x()+50)+"+"+str(root.winfo_y()+50))
    nroot.grab_set()
    tk.Label(nroot,text="更多选项").grid(row=0,column=0,rowspan=1,columnspan=3)
    tk.Button(nroot,text="检查对方客户端运行状态",command=clitest).grid(row=1,column=0,rowspan=1,columnspan=3)
    tk.Button(nroot,text="清空消息列表",command=clearing).grid(row=2,column=0,rowspan=1,columnspan=3)
    tk.Button(nroot,text="修复广播选项",command=resetcast).grid(row=3,column=0,rowspan=1,columnspan=3)
    nroot.mainloop()
root=tk.Tk()
root.title("Hello Foolish-man聊天器")
root.protocol('WM_DELETE_WINDOW', exit)


root.grid_rowconfigure(0, weight=10)
root.grid_rowconfigure(1, weight=10)
root.grid_rowconfigure(2, weight=10)
root.grid_rowconfigure(3, weight=10)
root.grid_rowconfigure(4, weight=10)
root.grid_rowconfigure(5, weight=10)
root.grid_rowconfigure(6, weight=3)




root.grid_columnconfigure(0, weight=1)
root.grid_columnconfigure(1, weight=1)
root.grid_columnconfigure(2, weight=1)
root.grid_columnconfigure(3, weight=1)
root.grid_columnconfigure(4, weight=1)
root.grid_columnconfigure(5, weight=1)
root.grid_columnconfigure(6, weight=1)



listb=tk.Listbox(root,selectmode=tk.SINGLE)
listb.grid(row=0,column=0,rowspan=6,columnspan=2,sticky="nsew")

buttonins=tk.Button(root,text="添加联系人",command=fr)
buttondel=tk.Button(root,text="删除联系人",command=dell)
# 移动按钮到第6行，避免与listb重叠
buttonins.grid(row=6,column=0,sticky="nsew")
buttondel.grid(row=6,column=1,sticky="nsew")

listm=tk.Listbox(root,width=40)
listm.grid(row=0,column=2,rowspan=6,columnspan=5,sticky="nsew")

e=tk.Entry(root,width=25)
# 移动e到listm不占据的列上，例如第7列
e.grid(row=6,column=2,rowspan=1,columnspan=3,sticky="nsew")
e.bind('<Return>', sent)
buttonsnd=tk.Button(root,text="发送",command=sent)
buttonsnd.grid(row=6,column=5,sticky="nsew")
buttonmor=tk.Button(root,text="更多…",command=more)
buttonmor.grid(row=6,column=6,sticky="nsew")
for k in d:
    listb.insert("end",k)
for i in mslist:
    listm.insert("end",i)
reload()
root.mainloop()


